package com.lry.router;

import com.lry.protocol.RpcInvocation;
import com.lry.proxy.Invoker;
import com.lry.spi.LoadLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 *  平滑加权轮询
 *  固定权重weight:A:1，B:2，C:3
 *  动态权重curWeight:A:0,B:0,C:0
 *  计算totalWeight6
 *  curWeight[i]+=weight[i]    (max curWeight)  返回ip   (max curWeight)-totalWeight
 *  curWeight：1，2，3                 3           C               1,2,-3
 *  curWeight:2,4,0                   4           B               2,-2,0
 *  curWeight:3,0,3                   3           A               -3,0,3
 *  curWeight:-2,2,6                  6           C               -2,2,0
 */

@LoadLevel(name = "roundRobin")
public class RoundRobinRouter implements Router {

    private List<RobinDto> curWeight;


    @Override
    public Invoker select(List<Invoker> invokers, RpcInvocation rpcInvocation) {
        if(null==curWeight){
            curWeight = new ArrayList<>(invokers.size());
            for(int i=0;i<invokers.size();i++){
                curWeight.add(null);
            }
        }else{
            if(curWeight.size()!=invokers.size()){
                handleNodeChange(invokers);
            }
        }

        int totalWeight = 0;
        for(int i=0;i<invokers.size();i++){
            Invoker invoker = invokers.get(i);
            totalWeight += invoker.getWeight();

            RobinDto robinDto = curWeight.get(i);
            if(null==robinDto){
                robinDto = new RobinDto(invoker.getWeight(),invoker.getIps());
            }else{
                robinDto.addWeight(invoker.getWeight());
            }
            curWeight.set(i,robinDto);

        }

        int maxWeightI = 0;
        int maxWeight = curWeight.get(0).getWeight();
        for(int i=1;i<curWeight.size();i++){
            if(curWeight.get(i).getWeight()>maxWeight){
                maxWeightI = i;
                maxWeight = curWeight.get(i).getWeight();
            }
        }

        curWeight.get(maxWeightI).addWeight(-totalWeight);

        return invokers.get(maxWeightI);
    }

    /**
     *
     * @param invokers new
     */
    private void handleNodeChange(List<Invoker> invokers) {
        Iterator<RobinDto> it = curWeight.iterator();
        while (it.hasNext()){
            RobinDto next = it.next();
            boolean f = false;
            for (Invoker invoker : invokers) {
                if(invoker.getIps().equals(next.getIp())){
                    f = true;
                    break;
                }
            }
            if(!f){
                it.remove();
            }
        }

        for (Invoker invoker : invokers) {
            boolean f = true;
            for (RobinDto robinDto : curWeight) {
                if(invoker.getIps().equals(robinDto.getIp())){
                    f = false;
                    break;
                }
            }
            if(f){
                curWeight.add(new RobinDto(invoker.getWeight(),invoker.getIps()));
            }
        }

    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    private class RobinDto{
        private int weight;
        private String ip;

        public void addWeight(int addWeight){
            this.weight += addWeight;
        }

    }
}
